package es.deusto.ingenieria.is.search.tsp.formulation;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import es.deusto.ingenieria.is.search.algorithms.Node;
import es.deusto.ingenieria.is.search.algorithms.SearchMethod;
import es.deusto.ingenieria.is.search.formulation.Problem;
import es.deusto.ingenieria.is.search.formulation.State;
import es.deusto.ingenieria.is.search.xml.StateXMLReader;

/**
 * @author Kevin e Inigo
 *
 *Clase que hereda de problem. Llama al metode de lectura de un archivo XML y devuelve dicho archivo.
 */
public class ProblemaTSP extends Problem{


	
	/**
	 * Constructor de ProblemaTSP. Para comprobar el correcto funcionamiento de createOpertors.
	 */
	public ProblemaTSP() {
		this.addInitialState(this.gatherInitialPercepts());
		createOperators();
	}

	/* (non-Javadoc)
	 * Lee y procesa el contenido del .xml e inicializa los parametros del enviroment
	 * @see es.deusto.ingenieria.is.search.formulation.Problem#gatherInitialPercepts()
	 * 
	 */
	public State gatherInitialPercepts() {
		
		StateXMLReader stateXMLReader = new MapReader("data/tourProblem1.xml"); 								
		return (EnvironmentMap) stateXMLReader.getState();
		 
	}

	/**
	 * Anade cada una de las acciones posible que podemos realizar en nuestro problema.
	 */
	protected void createOperators() {
		EnvironmentMap env = (EnvironmentMap) this.getInitialStates().get(0);
		for (int i=0; i<env.numCiudad -2; i++) {
			this.addOperator(new AnadirCiudad(env.ciudadesNoVisitadas.get(i)));
		}
		Collections.shuffle(this.getOperators());

	}


	/* (non-Javadoc)
	 * Comprueba si estamos en el estado final, para que se cumpla nuestro ArrayList de ciudades sin visitar deberia estar vacio.
	 * @see es.deusto.ingenieria.is.search.formulation.Problem#isFinalState(es.deusto.ingenieria.is.search.formulation.State)
	 * 
	 */
	public boolean isFinalState(State state) {
		if(state != null && state instanceof EnvironmentMap){

			return ((EnvironmentMap)state).getCiudadesNoVisitadas().size() == 0;
		}
		return false;
	}


	/** Devuelve el coste total de la solucion, mediante la suma de cada costo entre par de ciudades.
	 * @return double costeTotal
	 */
	public double getTotalCost(){
		double total = 0.0;
		EnvironmentMap env = (EnvironmentMap) this.getFinalStates().get(0);
		//paso inicial
		//Ciudad inicial=env.cInicial;
		//total+=Math.sqrt(Math.pow(env.ciudadesVisitadas.get(0).x-inicial.x, 2) + Math.pow(env.ciudadesVisitadas.get(0).y-inicial.y, 2));
		//System.out.println(total);
		//for(Operator o: this.getOperators()){
		//	System.out.println(o.getName()+" "+o.getCost());
		//	total += o.getCost();
		//}
		//total+=Math.sqrt(Math.pow(env.cFinal.x-env.ciudadesVisitadas.get(env.ciudadesVisitadas.size()-1).x, 2) + Math.pow(env.cFinal.x-env.ciudadesVisitadas.get(env.ciudadesVisitadas.size()-1).y, 2));

		//Calculandolo a mano funciona, los operadores van mal
		boolean primero=true;
		Ciudad anterior = null;
		for(Ciudad c: env.ciudadesVisitadas)
		{
			if(primero)
			{
				primero=false;
				total+=Math.sqrt(Math.pow(c.x-env.cInicial.x, 2) + Math.pow(c.y-env.cInicial.y, 2));
				
			}
			else
			{
				total+=Math.sqrt(Math.pow(c.x-anterior.x, 2) + Math.pow(c.y-anterior.y, 2));
			}
			anterior=c;
		}
		total+=Math.sqrt(Math.pow(env.cFinal.x-anterior.x, 2) + Math.pow(env.cFinal.y-anterior.y, 2));
		return total;
	}
	
	/** Metodo nos permite llevar a cabo el proceso de calculo de la ruta m�s optima, recibiendo como parametro el algoritmo a aplicar.
	 * @param searchMethod
	 */
	public void solve(SearchMethod searchMethod) {
		//Mostramos el inicio y final de la busqueda de la solucion del problema
		SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss.S");
		Date beginDate = GregorianCalendar.getInstance().getTime();
		System.out.println("\n* Inicio '" + searchMethod.getClass().getSimpleName() + "' (" + formatter.format(beginDate) + ")");				
		System.out.println("+++++"+this.getInitialStates().get(0)+"++++++");
		Node finalNode = searchMethod.search(this, this.getInitialStates().get(0));
		Date endDate = GregorianCalendar.getInstance().getTime();		
		System.out.println("* Final   '" + searchMethod.getClass().getSimpleName() + "' (" + formatter.format(endDate) + ")");
		
		//Calculamos el tiempo que ha tardado en diferentes periodos
		long miliseconds = (int) Math.abs(beginDate.getTime() - endDate.getTime());
		long seconds = miliseconds / 1000;
		miliseconds %= 1000;		
		long minutes = seconds / 60;
		seconds %= 60;
		long hours = minutes / 60;
		minutes %= 60;
		
		String time = "\n* La búsqueda dura: ";
		time += (hours > 0) ? hours + " h " : " ";
		time += (minutes > 0) ? minutes + " m " : " ";
		time += (seconds > 0) ? seconds + "s " : " ";
		time += (miliseconds > 0) ? miliseconds + "ms " : " ";
		
		System.out.println(time);
		
		//Si la busqueda ha sido exitosa, obtenemos la ruta de la solucion y creamos el fichero Log
		if (finalNode != null) {
			this.addFinalState(finalNode.getState());
			System.out.println("\n- Solucion encontrada!     ");
			List<String> operators = new ArrayList<String>();
			searchMethod.solutionPath(finalNode, operators);
			searchMethod.createSolutionLog(operators);
			System.out.println("- Estado final:\n" + finalNode.getState());
		} else {
			System.out.println("\n- No se ha encontrado solucion!     ");
		}
	}

}
